package p2pcdnsim.cdn;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import p2pcdnsim.exception.ClientDoesNotExistException;
import p2pcdnsim.exception.ClientInWrongComunityException;
import p2pcdnsim.exception.NodeUnexistentException;
import p2pcdnsim.exception.SurrogateAlreadyInsertedException;
import p2pcdnsim.network.NetworkAddress;
import p2pcdnsim.network.NetworkTopology;
import p2pcdnsim.network.Node;
import p2pcdnsim.network.RouterNode;

import desmoj.core.simulator.Model;
import desmoj.core.simulator.SimProcess;
import desmoj.core.simulator.SimTime;


public class CDN extends SimProcess {
	
	
	private ContentManager contentManager;
	private RequestRedirector redirector;
	private OriginServer origin;
	private NetworkTopology topology;
	
	private Map<Long,ClientComunity> clientPlacement;
	private Map<Long,ClientComunity> routerComunity;
	private Map<Surrogate,RouterNode> surrogatePlacement;
	private List<Surrogate> surrogates;
	private List<Request> requests;
	private RouterNode originServerNode;

	
	public CDN(Model owner, NetworkTopology topology) {
		super(owner,"CDN",true);
		this.topology = topology;
		this.clientPlacement = new HashMap<Long,ClientComunity>();
		this.routerComunity = new HashMap<Long,ClientComunity>();
		this.surrogatePlacement = new HashMap<Surrogate,RouterNode>();
		this.surrogates = new ArrayList<Surrogate>();
		requests = new ArrayList<Request>();
	}
	
	private void startNewClient(long clientID, int objectID) throws ClientInWrongComunityException {
		clientPlacement.get(clientID).startNewClient(clientID, objectID);
	}

	public void placeClient(long clientID, long router) throws NodeUnexistentException {
		RouterNode routerNode = topology.getNode(router);
		ClientComunity comunity;
		if(!routerComunity.containsKey(router)) {
			comunity = new ClientComunity(this,routerNode);
			routerComunity.put(router, comunity);
		} else comunity = routerComunity.get(router);
		comunity.addClient(clientID);
		clientPlacement.put(clientID, comunity);
	}


	public NetworkTopology getTopology() {
		return topology;
	}

	public void placeSurrogate(Surrogate surrogate, RouterNode node) throws SurrogateAlreadyInsertedException {
		if(surrogates.contains(surrogate)) throw new SurrogateAlreadyInsertedException();
		surrogates.add(surrogate);
		node.getNetworkLayer().getTransportLayer().getApplicationLayer().setApplicationProcess(surrogate);
		surrogatePlacement.put(surrogate, node);
		System.out.println("\tSurrogate "+surrogate+" was put on node "+node.getName());
	}

	public void placeRequestRedirector(RequestRedirector redirector, Node node) {
		this.redirector = redirector;
		node.getNetworkLayer().getTransportLayer().getApplicationLayer().setApplicationProcess(redirector);
	}

	public void placeOriginServer(OriginServer os, RouterNode node) {
		this.origin = os;
		this.originServerNode = node;
		node.getNetworkLayer().getTransportLayer().getApplicationLayer().setApplicationProcess(os);
	}
	
	public void addClientRequest(double time, long clientID, int objectID) throws ClientDoesNotExistException {
		//System.out.println("Adding client request on "+ time +": "+clientID+" - "+objectID);
		if(!clientPlacement.containsKey(clientID)) throw new ClientDoesNotExistException();
		requests.add(new Request(time,clientID,objectID));
	}
	
	@Override
	public void lifeCycle() {
	
		Collections.sort(requests);
		
		while(requests.size() > 0) {
			Request request = requests.remove(0);
			
			if(request.time > this.currentTime().getTimeValue()) {	
				if(this.isScheduled()) throw new RuntimeException("trying to hold but already scheduled at "+this.scheduledAt());
				hold(new SimTime(request.time - this.currentTime().getTimeValue()));
			}
			
			System.out.println("["+this.currentTime()+"] CDN: Executing request [Client:"+request.clientID+"-"+"Object:"+request.objectID+"]");
			try {
				this.startNewClient(request.clientID,request.objectID);
			} catch (ClientInWrongComunityException e) {
				e.printStackTrace();
				throw new RuntimeException(e);
			}
			
		}
	}

	public List<Surrogate> getSurrogates() {
		return this.surrogates;
	}
	
	public NetworkAddress getRequestRedirectorAddress() {
		return redirector.getNetworkAddress();
	}
	
	public NetworkAddress getOriginServerAddress() {
		return origin.getNetworkAddress();
	}
	
	public ContentManager getContentManager() {
		return contentManager;
	}

	public long getNodeOfClinet(long clientID) {
		return clientPlacement.get(clientID).node.getNodeId();
	}

	public OriginServer getOriginServer() {
		return origin;
	}

	public RouterNode getOriginServerNode() {
		return originServerNode;
	}

	public RouterNode getSurrogateNode(Surrogate s) {
		return surrogatePlacement.get(s);
	}

	private class Request implements Comparable<Request> {
		double time;
		long clientID;
		int objectID;
		
		public Request(double time,long clientID, int objectID) {
			this.time = time;
			this.clientID = clientID;
			this.objectID = objectID;
		}

		@Override
		public int compareTo(Request o) {
			return new Double(time).compareTo(new Double(o.time));
		}
	}
}
